// $Id: spaceship.cpp 532 2008-06-03 19:23:28Z agjermun $
// Implementation of the spaceship class
//
// ml278
// Copyright (C) 2008 Bjørn Øivind Bjørnsen, Aleksander Gjermundsen, Knut Andre Tidemann, Thomas Hansen
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

#include "Ogre.h"

#include "spaceship.h"
#include "colshapemanager.h"

using namespace Ogre;
/**
 * constructor with references to the scenemanager, scenenode, dynamicsworld in which the barrel 
 * is created and a string with the name of the barrel.
 * @param pMgr reference to the SceneManager of the scene
 * @param pNode reference to the SceneNode which contains the Barrel
 * @param world reference to the btDynamicsWorld (Bullet), which contains the Barrel
 * @param objectName name of the Barrel object
*/
SpaceShip::SpaceShip(SceneManager *pMgr, SceneNode *pNode, btDynamicsWorld *world, String objectName) : 
	BaseWorldObject(pMgr, pNode, world, String("SpaceShip.mesh"), objectName, Vector3(0, 0, 0))
{	
	initPhysics();
	
	for(int i=0;i<NUM_MOVEMENT_DIRS;i++)
	{
		mMovementDir[i] = false;
	}

	mAngularFactor = 0.5;		// Increase if you want faster turning acceleration
	mAngularMaxSpeed = 0.8;		// Increase if you want faster maximum turning speed
}

/**
 * destructor
*/
SpaceShip::~SpaceShip()
{
	exitPhysics();
	delete mColKey;
}

/**
 * function called to update the position of the SpaceShip
*/
void SpaceShip::update(Real elapsedTime)
{
	// Apply movement for "digital" input (ie. keyboard)
	if(mMovementDir[FORWARD])
	{
		if(mBody->getLinearVelocity().length() < 200)
		{
				btScalar force = 2000.0f;
				fireMainThruster(force);
		}
	}
	if(mMovementDir[BACKWARD])
	{
		if(mBody->getLinearVelocity().length() < 200)
		{
			btScalar force = 2000.0f;
			fireReverseThruster(force);
		}
	}
	if(mMovementDir[YAW_LEFT])
	{
		btScalar yaw = mAngularFactor * elapsedTime;

		btVector3 yAxis = getYVector();
		btVector3 angularVel = mBody->getAngularVelocity();
		btVector3 yawVel = angularVel * yAxis;

        // Apply yaw if it the new speed is within the preset limit
		if((yawVel.length() + yaw) < mAngularMaxSpeed)
		{
			fireYawThruster(yaw);
		}
	}
	if(mMovementDir[YAW_RIGHT])
	{
		btScalar yaw = -mAngularFactor * elapsedTime;

		btVector3 yAxis = getYVector();
		btVector3 angularVel = mBody->getAngularVelocity();
		btVector3 yawVel = angularVel * yAxis;

        // Apply yaw if it the new speed is within the preset limit
		if((yawVel.length() + yaw) < mAngularMaxSpeed)
		{
			fireYawThruster(yaw);
		}
	}
	if(mMovementDir[PITCH_UP])
	{
		btScalar pitch = -mAngularFactor * elapsedTime;

		btVector3 xAxis = getXVector();
		btVector3 angularVel = mBody->getAngularVelocity();
		btVector3 pitchVel = angularVel * xAxis;

        // Apply pitch if it the new speed is within the preset limit
		if((pitchVel.length() + pitch) < mAngularMaxSpeed)
		{
			firePitchThruster(pitch);
		}
	}
	if(mMovementDir[PITCH_DOWN])
	{
		btScalar pitch = mAngularFactor * elapsedTime;

		btVector3 xAxis = getXVector();
		btVector3 angularVel = mBody->getAngularVelocity();
		btVector3 pitchVel = angularVel * xAxis;

        // Apply pitch if it the new speed is within the preset limit
		if((pitchVel.length() + pitch) < mAngularMaxSpeed)
		{
			firePitchThruster(pitch);
		}
	}

        btTransform trans;
        btDefaultMotionState *motionState = (btDefaultMotionState*)mBody->getMotionState();
        motionState->getWorldTransform(trans);

        btVector3 pos = trans.getOrigin();

        mNode->setPosition(Vector3(pos.getX(), pos.getY(), pos.getZ()));
        mNode->setOrientation(trans.getRotation().getW(),trans.getRotation().getX(), trans.getRotation().getY(), trans.getRotation().getZ());
}

/**
 * sets the position of the SpaceShip in the physics-world
 * @param pos the position vector for the destination
*/
void SpaceShip::setPos(Vector3 &pos)
{
        mBody->getWorldTransform().setOrigin(btVector3(pos.x, pos.y, pos.z));
	BaseWorldObject::setPos(pos);
}

// Change visibility - Useful for 1st person view ;)
void SpaceShip::setVisible (bool visible) 
{
        mNode->setVisible (visible);
}

/**
 * Returns the orientation of the spaceship as a quaternion.
*/
const Quaternion& SpaceShip::getOrientation()
{
        return mNode->getOrientation();
}

void SpaceShip::initPhysics()
{
        // XXX stub
    	mColKey = new ColShapeManager::ColShapeKey(ColShapeManager::box, mHalfSize);
    	ColShapeManager *temp = ColShapeManager::getSingletonPtr();
    	mColShape = temp->getShape(*mColKey);

    	// Create physics stuff. TODO: static atm
    	btScalar mass(10000.0);
    	btTransform startTransform;
    	btVector3 localInertia(0,0,0);

    	mColShape->calculateLocalInertia(mass,localInertia);

    	startTransform.setIdentity();
    	startTransform.setOrigin(btVector3(mPosition.x, mPosition.y, mPosition.z));

	btDefaultMotionState *motionState = new btDefaultMotionState(startTransform);
	btRigidBody::btRigidBodyConstructionInfo rbInfo(mass,motionState,mColShape,localInertia);
	mBody = new btRigidBody(rbInfo);
	
        mBody->setActivationState(DISABLE_DEACTIVATION);

	//higher the number, longer until stop.
	btScalar linDamping(0.6);
	btScalar angDamping(0.6);
	mBody->setDamping(linDamping, angDamping);
	
        mWorld->addRigidBody(mBody);
}

void SpaceShip::exitPhysics()
{
        ColShapeManager *temp = ColShapeManager::getSingletonPtr();
    	temp->removeShape(*mColKey);
    	// TODO: delete motionstate
    	mWorld->removeRigidBody(mBody);
}

/** 
 * Starts movement in the given direction.
 * Will automatically dampen the movement (used for "digital" input control, ie. keyboard).
 * @param dir type of movement to be started
 * @see removeMovementDir()
 */
void SpaceShip::applyMovementDir(ShipMovement dir)
{
	mMovementDir[dir] = true;
}

/** 
 * Stop movement in the given direction.
 * Will automatically dampen the movement (used for "digital" input control, ie. keyboard).
 * @param dir type of movement to be stopped
 * @see applyMovementDir()
 */
void SpaceShip::removeMovementDir(ShipMovement dir)
{
	mMovementDir[dir] = false;
}

/** 
 * Adds a given force to the Z-axis of the spaceship
 * @param force the force to add to the Z-axis 
 */
void SpaceShip::fireRollThruster(btScalar & force)
{
	btVector3 zAxis = getZVector();
	btVector3 angularVel = mBody->getAngularVelocity();
	
	angularVel += (zAxis * force);
	mBody->setAngularVelocity(angularVel);
}

/** 
 * Adds a given force to the X-axis of the spaceship
 * @param force the force to add to the X-axis 
 */
void SpaceShip::firePitchThruster(btScalar & force)
{
	btVector3 xAxis = getXVector();
	btVector3 angularVel = mBody->getAngularVelocity();
	
	angularVel += (xAxis * force);
	mBody->setAngularVelocity(angularVel);
}

/** Adds a given force to the Y-axis of the spaceship
 * @param force the force to add to the Y-axis 
 */
void SpaceShip::fireYawThruster(btScalar & force)
{
	btVector3 yAxis = getYVector();
	btVector3 angularVel = mBody->getAngularVelocity();
	
	angularVel += (yAxis * force);
	mBody->setAngularVelocity(angularVel);
}

/**
 * Adds a given force in the direction the ship is facing
 * @param force the force to add to the forward direction
 */
void SpaceShip::fireMainThruster(btScalar & force)
{
	// The Z-vector is the same as the forward direction
	btVector3 forwardVector = getZVector();

	mBody->applyCentralImpulse(forwardVector * force);
	// Uncomment for debug output
	//LogManager::getSingletonPtr()->logMessage("speed (length) is: " + StringConverter::toString(mBody->getLinearVelocity().length()) );
}

/**
 * Adds a given force to the reverse of the direction the ship is facing
 * @param force the force to add to the reverse direction (subtract from the forward direction)
 */
void SpaceShip::fireReverseThruster(btScalar & force)
{
	// The Z-vector is the same as the forward direction
	btVector3 forwardVector = getZVector();
	mBody->applyCentralImpulse(-forwardVector * force);
}

/**
 * Turns the ship the spesified angle around the x-axis.
 * This applies the transformation directly and should not be used for user control.
 * @param angle angle to turn (radians)
*/
void SpaceShip::turnAngleAroundXAxis(btScalar &angle)
{
	btTransform xForm;
	// Retrieve current world transformation
	mBody->getMotionState()->getWorldTransform (xForm);
	
	// Modify the transformation (rotate)
	xForm.setRotation (btQuaternion (btVector3(0.0, 1.0, 0.0), angle));
	
	// Apply the new transformation
	mBody->getMotionState()->setWorldTransform (xForm);
	mBody->setCenterOfMassTransform (xForm);
}

/**
 * Turns the ship the spesified angle around the y-axis.
 * This applies the transformation directly and should not be used for user control.
 * @param angle angle to turn (radians)
*/
void SpaceShip::turnAngleAroundYAxis(btScalar &angle)
{
	btTransform yForm;
	// Retrieve current world transformation
	mBody->getMotionState()->getWorldTransform (yForm);
	
	// Modify the transformation (rotate)
	yForm.setRotation (btQuaternion (btVector3(0.0, 1.0, 0.0), angle));
	
	// Apply the new transformation
	mBody->getMotionState()->setWorldTransform (yForm);
	mBody->setCenterOfMassTransform (yForm);
}

/**
 * Turns the ship the spesified angle around the y-axis.
 * This applies the transformation directly and should not be used for user control.
 * @param angle angle to turn (radians)
*/
void SpaceShip::turnAngleAroundZAxis(btScalar &angle)
{
	btTransform zForm;
	// Retrieve current world transformation
	mBody->getMotionState()->getWorldTransform (zForm);
	
	// Modify the transformation (rotate)
	zForm.setRotation (btQuaternion (btVector3(0.0, 1.0, 0.0), angle));
	
	// Apply the new transformation
	mBody->getMotionState()->setWorldTransform (zForm);
	mBody->setCenterOfMassTransform (zForm);
}

/**
 * Sets the maximum turning speed when using the apply/removeMovement-functions.
 * @param speed maximum speed
*/
void SpaceShip::setAngularMaxSpeed(double speed)
{
	mAngularMaxSpeed = speed;
}

/**
 * Sets the turning acceleration when using the apply/removeMovement-functions.
 * @param factor new factor of acceleration
*/
void SpaceShip::setAngularFactor(double factor)
{
	mAngularFactor = factor;
}

/**
 * Returns the current maximum turning speed when using the apply/removeMovement-functions.
 * @return the current maximum turning speed
*/
double SpaceShip::getAngularMaxSpeed()
{
	return mAngularMaxSpeed;
}

/**
 * Returns the current turning acceleration factor when using the apply/removeMovement-functions.
 * @return the current turning acceleration factor
*/
double SpaceShip::getAngularFactor()
{
	return mAngularMaxSpeed;
}



